// @File  : local.go
// @Author: JunLong.Liao&此处不应有BUG!
// @Date  : 2021/5/26
// @slogan: 又是不想写代码的一天，神兽保佑，代码无BUG！
//         ┏┓      ┏┓
//        ┏┛┻━━━━━━┛┻┓
//        ┃     ღ    ┃
//        ┃  ┳┛   ┗┳ ┃
//        ┃     ┻    ┃
//        ┗━┓      ┏━┛
//          ┃      ┗━━━┓
//          ┃ 神兽咆哮!  ┣┓
//          ┃         ┏┛
//          ┗┓┓┏━━━┳┓┏┛
//           ┃┫┫   ┃┫┫
//           ┗┻┛   ┗┻┛

package CloudStorage

import (
	"encoding/base64"
	"errors"
	"gitee.com/diunigedaxigua/cloud-storage/tools"
	"github.com/nfnt/resize"
	"image"
	"image/gif"
	"image/jpeg"
	"image/png"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"mime/multipart"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"
	"time"
)

type Local struct {
	Path string `mapstructure:"path" json:"path" yaml:"path"` // 本地文件路径
}

// 本地上传
type LocalOSS interface {
	IsFileExist(filename string) bool                                                                  // 检查文件是否存在
	UploadFile(file *multipart.FileHeader) (path, filename string, err error)                          // 上传文件
	DeleteFile(key string) error                                                                       // 删除文件
	ImageClip(filename string, wi, hi, x0, y0, x1, y1, quality int) (Path, FileName string, err error) // 裁剪图片
	ImageScale(filename string, width, height, quality int) (Path, FileName string, err error)         // 缩略图
	UploadFileByBase64(ImageContent string) (Path, Filename string, err error)                         // base64上传图片
}

// 实例本地上传
func NewLocalOSS(c *Local) LocalOSS {
	return &Local{
		Path: c.Path,
	}
}

// @author: [wulala](https://github.com/water-gulugulu)
// @object: *LocalUpload
// @function: UploadFile
// @description: 上传文件
// @param: file *multipart.FileHeader
// @return: Path string
// @return: FileName string
// @return: err error
func (l *Local) UploadFile(file *multipart.FileHeader) (Path, FileName string, err error) {
	// 读取文件后缀
	ext := path.Ext(file.Filename)
	// 读取文件名并加密
	name := strings.TrimSuffix(file.Filename, ext)
	name = tools.MD5V([]byte(name))
	// 拼接新文件名
	filename := name + "_" + time.Now().Format("20060102150405") + ext
	// 尝试创建此路径
	mkdirErr := os.MkdirAll(l.Path, os.ModePerm)
	if mkdirErr != nil {
		return "", "", errors.New("function os.MkdirAll() Filed, err:" + mkdirErr.Error())
	}
	// 拼接路径和文件名
	p := l.Path + "/" + filename

	f, openError := file.Open() // 读取文件
	if openError != nil {
		return "", "", errors.New("function file.Open() Filed, err:" + openError.Error())
	}

	out, createErr := os.Create(p)
	if createErr != nil {
		return "", "", errors.New("function os.Create() Filed, err:" + createErr.Error())
	}

	defer func() {
		_ = f.Close()
		_ = out.Close()
	}()

	_, copyErr := io.Copy(out, f) // 传输（拷贝）文件
	if copyErr != nil {
		return "", "", errors.New("function io.Copy() Filed, err:" + copyErr.Error())
	}
	return p, filename, nil
}

// @author: [wulala](https://github.com/water-gulugulu)
// @object: *LocalUpload
// @function: DeleteFile
// @description: 删除文件
// @param: key string
// @return: error
func (l *Local) DeleteFile(key string) error {
	p := l.Path + "/" + key
	if strings.Contains(p, l.Path) {
		if err := os.Remove(p); err != nil {
			return errors.New("本地文件删除失败, err:" + err.Error())
		}
	}
	return nil
}

// @author: [wulala](https://github.com/water-gulugulu)
// @author: [wulala](https://gitee.com/diunigedaxigua)
// @function: ClipFile
// @description: 图片裁剪
// @param: filename string 要裁剪的文件名，会和Local.Path拼接组成文件名
// @param: wi int 裁剪宽度，传0则默认裁剪宽度 如原图宽为100px x0:10 x1:90 那么成图宽为x1-x0=80px
// @param: hi int 裁剪高度，传0则默认裁剪高度 如原图高为100px y0:10 y1:90 那么成图高为y1-y0=80px
// @param: x0 int 裁剪宽坐标，从多少像素开始裁剪，如原图100px x0:10那么从10像素开始裁剪
// @param: y0 int 裁剪高坐标，从多少像素开始裁剪，如原图100px y0:10那么从10像素开始裁剪
// @param: x1 int 裁剪宽坐标，从x0开始到x1像素裁剪结束，如原图100px x0:10,x1:30那么从10像素开始裁剪到40像素结束
// @param: y1 int 裁剪高坐标，从y0开始到y1像素裁剪结束，如原图100px y0:10,y1:30那么从10像素开始裁剪到40像素结束
// @param: quality int 像素精度 百分比
// @return: Path string 文件访问路径（带文件名）
// @return: FileName string 文件名
// @return: err error 错误信息
func (l *Local) ImageClip(filename string, width, height, x0, y0, x1, y1, quality int) (Path, FileName string, err error) {
	var in, out *os.File
	// 打开文件
	in, err = os.Open(l.Path + "/" + filename)
	if err != nil {
		log.Println(err)
		return
	}
	// 替换文件名加上_small
	FileName = strings.Replace(filename, ".", "_small.", 1)
	Path = l.Path + "/" + FileName
	out, err = os.Create(Path)
	if err != nil {
		log.Println(err)
		return
	}
	defer func() {
		_ = in.Close()
		_ = out.Close()
		if r := recover(); r != nil {
			log.Println(r)
		}
	}()

	var origin, canvas image.Image
	var fm string
	origin, fm, err = image.Decode(in)
	if err != nil {
		log.Println(err)
		return
	}
	// 如果未设置宽高那么就默认截取后宽高，如果设置了宽高，那么计算最后截取剩下的宽高
	if width == 0 || height == 0 {
		width = origin.Bounds().Max.X
		height = origin.Bounds().Max.Y
	} else {
		x1 += width  // x1 加上最后要截取的宽度
		y1 += height // y1 加上最后要截取的高度
		// 如果大于原图宽度 那么就设置最大为原图宽度
		if x1 > origin.Bounds().Max.X {
			x1 = origin.Bounds().Max.X
		}
		// 如果大于原图高度 那么就设置最大为原图高度
		if y1 > origin.Bounds().Max.Y {
			y1 = origin.Bounds().Max.Y
		}
	}
	canvas = origin
	if quality == 0 {
		quality = 100
	}
	// 图片处理
	switch fm {
	case "jpeg":
		img := canvas.(*image.YCbCr)
		subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.YCbCr)
		subImg2 := resize.Thumbnail(uint(width), uint(height), subImg, resize.Lanczos3)
		err = jpeg.Encode(out, subImg2, &jpeg.Options{quality})
		return
	case "png":
		switch canvas.(type) {
		case *image.NRGBA:
			img := canvas.(*image.NRGBA)
			subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.NRGBA)
			subImg2 := resize.Thumbnail(uint(width), uint(height), subImg, resize.Lanczos3)
			err = png.Encode(out, subImg2)
			return
		case *image.RGBA:
			img := canvas.(*image.RGBA)
			subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.RGBA)
			subImg2 := resize.Thumbnail(uint(width), uint(height), subImg, resize.Lanczos3)
			err = png.Encode(out, subImg2)
			return
		}
	case "gif":
		img := canvas.(*image.Paletted)
		subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.Paletted)
		subImg2 := resize.Thumbnail(uint(width), uint(height), subImg, resize.Lanczos3)
		err = gif.Encode(out, subImg2, &gif.Options{})
		return
	// case "bmp":
	// 	img := canvas.(*image.RGBA)
	// 	subImg := img.SubImage(image.Rect(x0, y0, x1, y1)).(*image.RGBA)
	// 	subImg2 := resize.Thumbnail(uint(width), uint(height), subImg, resize.Lanczos3)
	// 	err = bmp.Encode(out, subImg2)
	// 	return
	default:
		err = errors.New("ERROR FORMAT")
		return
	}
	return
}

// @author: [wulala](https://github.com/water-gulugulu)
// @author: [wulala](https://gitee.com/diunigedaxigua)
// @function: ImageScale
// @description: 缩略图生成
// @param: filename string 要裁剪的文件名，会和Local.Path拼接组成文件名
// @param: width int 裁剪宽度，传0则默认裁剪宽度 如原图宽为100px x0:10 x1:90 那么成图宽为x1-x0=80px
// @param: height int 裁剪高度，传0则默认裁剪高度 如原图高为100px y0:10 y1:90 那么成图高为y1-y0=80px
// @param: quality int 像素精度 百分比
// @return: Path string 文件访问路径（带文件名）
// @return: FileName string 文件名
// @return: err error 错误信息
func (l *Local) ImageScale(filename string, width, height, quality int) (Path, FileName string, err error) {
	var in, out *os.File
	in, err = os.Open(l.Path + "/" + filename)
	if err != nil {
		log.Println(err)
		return
	}
	// 替换文件名加上_small
	FileName = strings.Replace(filename, ".", "_thumbnail.", 1)
	Path = l.Path + "/" + FileName
	out, err = os.Create(Path)
	if err != nil {
		log.Println(err)
		return
	}
	defer func() {
		_ = in.Close()
		_ = out.Close()
		if r := recover(); r != nil {
			log.Println(r)
		}
	}()
	origin, fm, err := image.Decode(in)
	if err != nil {
		log.Println(err)
		return
	}
	if width == 0 || height == 0 {
		width = origin.Bounds().Max.X
		height = origin.Bounds().Max.Y
	}
	if quality == 0 {
		quality = 100
	}
	canvas := resize.Thumbnail(uint(width), uint(height), origin, resize.Lanczos3)

	switch fm {
	case "jpeg":
		err = jpeg.Encode(out, canvas, &jpeg.Options{quality})
	case "png":
		err = png.Encode(out, canvas)
	case "gif":
		err = gif.Encode(out, canvas, &gif.Options{})
	// case "bmp":  //被我注释掉的是x/image/bmp
	//	return w, h, bmp.Encode(out, canvas)
	default:
		err = errors.New("ERROR FORMAT")
	}
	return
}

// @author: [wulala](https://github.com/water-gulugulu)
// @author: [wulala](https://gitee.com/diunigedaxigua)
// @function: IsFileExist
// @description: 检查文件是否存在
// @param: filename string 要检查的文件名，会和Local.Path拼接组成文件名
// @return: bool true:存在，false:不存在
func (l *Local) IsFileExist(filename string) bool {
	_, err := os.Stat(l.Path + "/" + filename)
	if os.IsNotExist(err) {
		return false
	}
	return true
}

// @author: [wulala](https://github.com/water-gulugulu)
// @author: [wulala](https://gitee.com/diunigedaxigua)
// @function: UploadFileByBase64
// @description: 上传base64保存到图片
// @param: ImageContent string 图片的base64编码字符串
// @return: Path string 文件访问路径（带文件名）
// @return: FileName string 文件名
// @return: err error 错误信息
func (l *Local) UploadFileByBase64(ImageContent string) (Path, Filename string, err error) {
	b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, ImageContent)
	if !b {
		return "", "", errors.New("图片为空")
	}

	re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
	// allData := re.FindAllSubmatch([]byte(ImageContent), 2)
	// fileType := string(allData[0][1]) // png ，jpeg 后缀获取
	// if len(fileType) == 0 {
	// 	fileType = ".jpg"
	// }
	base64Str := re.ReplaceAllString(ImageContent, "")

	curFileStr := strconv.FormatInt(time.Now().UnixNano(), 10)

	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	n := r.Intn(99999)

	name := tools.MD5V([]byte(curFileStr + strconv.Itoa(n)))
	// 拼接新文件名
	filename := name + "_" + time.Now().Format("20060102150405") + ".jpg"

	var file = l.Path + "/" + filename
	byte, err := base64.StdEncoding.DecodeString(base64Str)
	if err != nil {
		return "", "", err
	}

	if err = ioutil.WriteFile(file, byte, 0777); err != nil {
		return "", "", err
	}

	return l.Path + "/" + filename, filename, nil
}
